/*
 * Copyright 2002-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hsc.lock.annotation;

import com.hsc.lock.interceptor.CacheEvictOperation;
import com.hsc.lock.interceptor.CacheableOperation;
import com.hsc.lock.interceptor.LockPutOperation;
import com.hsc.lock.interceptor.ReplayLockOperation;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * Strategy implementation for parsing Spring's {@link com.hsc.lock.annotation.Caching}, {@link com.hsc.lock.annotation.Cacheable},
 * {@link com.hsc.lock.annotation.CacheEvict}, and {@link com.hsc.lock.annotation.CachePut} annotations.
 *
 * @author Costin Leau
 * @author Juergen Hoeller
 * @author Chris Beams
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Sam Brannen
 * @since 3.1
 */
@SuppressWarnings("serial")
public class SpringCacheAnnotationParser implements LockAnnotationParser, Serializable {

    private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = new LinkedHashSet<>(8);

    static {
        CACHE_OPERATION_ANNOTATIONS.add(ReplayLock.class);
        CACHE_OPERATION_ANNOTATIONS.add(ReplayLockConfig.class);
    }


    @Override
    public boolean isCandidateClass(Class<?> targetClass) {
        return AnnotationUtils.isCandidateClass(targetClass, CACHE_OPERATION_ANNOTATIONS);
    }

    @Override
    @Nullable
    public Collection<ReplayLockOperation> parseCacheAnnotations(Class<?> type) {
        DefaultReplayLockConfig defaultConfig = new DefaultReplayLockConfig(type);
        return parseCacheAnnotations(defaultConfig, type);
    }

    @Override
    @Nullable
    public Collection<ReplayLockOperation> parseCacheAnnotations(Method method) {
        DefaultReplayLockConfig defaultConfig = new DefaultReplayLockConfig(method.getDeclaringClass());
        return parseCacheAnnotations(defaultConfig, method);
    }

    @Nullable
    private Collection<ReplayLockOperation> parseCacheAnnotations(DefaultReplayLockConfig cachingConfig, AnnotatedElement ae) {
        Collection<ReplayLockOperation> ops = parseCacheAnnotations(cachingConfig, ae, false);
        if (ops != null && ops.size() > 1) {
            // More than one operation found -> local declarations override interface-declared ones...
            Collection<ReplayLockOperation> localOps = parseCacheAnnotations(cachingConfig, ae, true);
            if (localOps != null) {
                return localOps;
            }
        }
        return ops;
    }

    @Nullable
    private Collection<ReplayLockOperation> parseCacheAnnotations(
            DefaultReplayLockConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {

        Collection<? extends Annotation> anns = (localOnly ?
                AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) :
                AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));
        if (anns.isEmpty()) {
            return null;
        }

        final Collection<ReplayLockOperation> ops = new ArrayList<>(1);

        anns.stream().filter(ann -> ann instanceof ReplayLock).forEach(
                ann -> ops.add(parseReplayLockAnnotation(ae, cachingConfig,  (ReplayLock)ann)));
//        anns.stream().filter(ann -> ann instanceof com.hsc.lock.annotation.CacheEvict).forEach(
//                ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (com.hsc.lock.annotation.CacheEvict) ann)));
//        anns.stream().filter(ann -> ann instanceof com.hsc.lock.annotation.CachePut).forEach(
//                ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (com.hsc.lock.annotation.CachePut) ann)));
//        anns.stream().filter(ann -> ann instanceof com.hsc.lock.annotation.Caching).forEach(
//                ann -> parseCachingAnnotation(ae, cachingConfig, (com.hsc.lock.annotation.Caching) ann, ops));
        return ops;
    }



    private ReplayLockOperation parseReplayLockAnnotation(
            AnnotatedElement ae, DefaultReplayLockConfig defaultConfig, ReplayLock  replayLock) {
        ReplayLockOperation  replayLockOperation=new ReplayLockOperation();


        replayLockOperation.setKey(replayLock.key());
        replayLockOperation.setKeyGenerator(replayLock.keyGenerator());
        replayLockOperation.setKeyTtl(replayLock.keyTtl());

        replayLockOperation.setRemoveKeyAfterReturn(replayLock.removeKeyAfterReturn());
        replayLockOperation.setRemoveKeyAfterException(replayLock.removeKeyAfterException());

        replayLockOperation.setRefuseEnterException(replayLock.refuseEnterException());
        replayLockOperation.setRefuseEnterMessage(replayLock.refuseEnterMessage());
        replayLockOperation.setRefuseEnterHandler(replayLock.refuseEnterHandler());

        replayLockOperation.setReentrant(replayLock.reentrant());

        defaultConfig.applyDefault(replayLockOperation);
        validateCacheOperation(ae, replayLockOperation);

        return replayLockOperation;
    }



    /**
     * Validates the specified {@link ReplayLockOperation}.
     * <p>Throws an {@link IllegalStateException} if the state of the operation is
     * invalid. As there might be multiple sources for default values, this ensure
     * that the operation is in a proper state before being returned.
     *
     * @param ae        the annotated element of the cache operation
     * @param operation the {@link ReplayLockOperation} to validate
     */
    private void validateCacheOperation(AnnotatedElement ae, ReplayLockOperation operation) {
        if (StringUtils.hasText(operation.getKey()) && StringUtils.hasText(operation.getKeyGenerator())) {
            throw new IllegalStateException("Invalid cache annotation configuration on '" +
                    ae.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
                    "These attributes are mutually exclusive: either set the SpEL expression used to" +
                    "compute the key at runtime or set the name of the KeyGenerator bean to use.");
        }
//        if (StringUtils.hasText(operation.getCacheManager()) && StringUtils.hasText(operation.getCacheResolver())) {
//            throw new IllegalStateException("Invalid cache annotation configuration on '" +
//                    ae.toString() + "'. Both 'cacheManager' and 'cacheResolver' attributes have been set. " +
//                    "These attributes are mutually exclusive: the cache manager is used to configure a" +
//                    "default cache resolver if none is set. If a cache resolver is set, the cache manager" +
//                    "won't be used.");
//        }
    }

    @Override
    public boolean equals(@Nullable Object other) {
        return (this == other || other instanceof SpringCacheAnnotationParser);
    }

    @Override
    public int hashCode() {
        return SpringCacheAnnotationParser.class.hashCode();
    }



}
